<?php
/**
 * Core_Acl
 * 
 * @author naspeh
 * @version $Id: Acl.php 661 2008-06-16 10:38:02Z naspeh $
 */
class Core_Acl extends Zend_Acl 
{
    /**
     * Флаг, разрешающий изменение БД
     *
     * @var boolean
     */
    protected $_isUpdateDb = false;
    /**
     * Класс плагина
     *
     * @var string
     */
    protected $_pluginClass = 'Core_Acl_Controller_Plugin';
    /**
     * Конструктор
     *
     */
    public function __construct()
    {
        $this->addRulesByWhere('(resource_id is null) and (privilege_id is null)');
    }
    /**
     * Стартует
     *
     * @return Core_Acl
     */
    public static function start()
    {
        if (!Core_Auth::isStarted()) {
        	Core_Auth::start();
        }
        $acl = self::getRegisrty(); 
        $acl->activeRole(new Core_Acl_Role(Core_Auth::get('name')));
                
        $front = Core_Controller_Front::getInstance();
        if (!$front->hasPlugin($acl->getPluginClass())) {
            $pluginClass = $acl->getPluginClass();
        	$front->registerPlugin(new $pluginClass());
        }
    }
    /**
     * Возвращает класс плагина
     *
     * @return string
     */
    public function getPluginClass()
    {
        return $this->_pluginClass;
    }
    /**
     * Устанавливает флаг, разрешающий изменение БД
     *
     * @param boolean $flag
     * @return Core_Acl
     */
    public function setIsUpadteDb($flag)
    {
        $this->_isUpdateDb = $flag;
        return $this;
    }
    /**
     * Возвращает флаг, разрешающий изменение БД
     *
     * @return boolean
     */
    public function isUpdateDb()
    {
        return $this->_isUpdateDb;
    }
    /**
     * Активирует ресурс
     *
     * @param Core_Acl_Resource_Abstract $resource
     * @return Core_Acl
     */
    public function active(Core_Acl_Resource_Abstract $resource)
    {
        $resource->active($this);
        return $this;
    }
    /**
     * Активирует роль
     *
     * @param Core_Acl_Role $role
     * @return Core_Acl
     */
    public function activeRole(Core_Acl_Role $role)
    {
        $role->active($this);
        return $this;
    }    
    /**
     * Добавляет ресурс
     *
     * @param Zend_Acl_Resource_Interface $resource
     * @param array|string|Zend_Acl_Resource_Interface|null $parent
     * @return Core_Acl
     */
    public function add(Zend_Acl_Resource_Interface $resource, $parent = null)
    {
        parent::add($resource, $parent);
        $this->addRulesByWhere(array('resource_id = ?' => $resource->getResourceId()));
        return $this;
    }
    /**
     * Добавляет роль
     *
     * @param Zend_Acl_Role_Interface $role
     * @param array|string|Zend_Acl_Role_Interface $parent
     * @return Core_Acl
     */
    public function addRole(Zend_Acl_Role_Interface $role, $parents = null)
    {
        parent::addRole($role, $parents);
        $this->addRulesByWhere(array('role_id = ?' => $role->getRoleId()));
        return $this;
    }
    /**
     * Добавляет правило по условию
     *
     * @param string|array|Zend_Db_Table_Select $where
     * @return Core_Acl
     */
    public function addRulesByWhere($where)
    {
        $tableRules = new Core_Table_Rules();
        $rowset = $tableRules->fetchAll($where);
        foreach ($rowset as $row) {
            if ($row->is_allowed) {
                $method = 'allow';
            } else {
                $method = 'deny';;
            }
            if ((is_null($row->role_id) || $this->hasRole($row->role_id)) && (is_null($row->resource_id) || $this->has($row->resource_id))) {
                $this->$method($row->role_id, $row->resource_id, $row->privilege_id);
            }
        }
        return $this;
    }
    /**
     * Добавляет правило
     *
     * @param  string $operation
     * @param  string $type
     * @param  Zend_Acl_Role_Interface|string|array $roles
     * @param  Zend_Acl_Resource_Interface|string|array $resources
     * @param  string|array $privileges
     * @param  Zend_Acl_Assert_Interface $assert
     * @return Core_Acl
     */
    public function setRule($operation, $type, $roles = null, $resources = null, $privileges = null,
                            Zend_Acl_Assert_Interface $assert = null)
    {
        if ($this->isUpdateDb()) {
            parent::setRule($operation, & $type, & $roles, & $resources, & $privileges, $assert);
            $tableRules = new Core_Table_Rules();
            if (0 == count($privileges)) {
                $privileges = array(null);
            }
            foreach ($resources as $resource) {
                $where = array();
                if (is_null($resource)) {
                    $where[] = 'resource_id is null ';
                } else {
                    $resource = $resource->getResourceId();
                    $where['resource_id = ?'] = $resource;
                }
                foreach ($roles as $role) {
                    if (is_null($role)) {
                        $where[] = 'role_id is null ';
                    } else {
                        $role = $role->getRoleId();
                        $where['role_id = ?'] =  $role;
                    }
                    foreach ($privileges as $privilege) {
                        if (is_null($privilege)) {
                            $where[] = 'privilege_id is null';
                        } else {
                            $where['privilege_id = ?'] = $privilege;
                        }
                        switch ($operation) {
                            case self::OP_ADD:
                                if (self::TYPE_ALLOW == $type) {
                                    $typeValue = 1;
                                } else {
                                    $typeValue = 0;
                                }                           
                                if (null == ($row = $tableRules->fetchRow($where))) {
                                    $data = array(
                                       'resource_id' => $resource,
                                       'role_id' => $role,
                                       'privilege_id' => $privilege,
                                       'is_allowed' => $typeValue
                                    );
                                    $tableRules->insert($data);
                                } else {
                                    $row->is_allowed = $typeValue;
                                    $row->save();
                                }
                                break;
                            case self::OP_REMOVE:
                            default:
                                if (null != ($row = $tableRules->fetchRow($where))) {
                                    $row->delete();
                                }
                                break;
                        }
                    }
                }
            }
        } else {
        	parent::setRule($operation, $type, $roles, $resources, $privileges, $assert);
        }
        return $this;
    }
    /**
     * Возвращает флаг разрешения
     *
     * @param  Zend_Acl_Role_Interface|string     $role
     * @param  Zend_Acl_Resource_Interface|string $resource
     * @param  string                             $privilege
     * @uses   Zend_Acl::get()
     * @uses   Zend_Acl_Role_Registry::get()
     * @return boolean
     */
    public function isAllowed($role = null, $resource = null, $privilege = null)
    {
        if (is_null($privilege)) {
            $privilege = 0;
        }
        return parent::isAllowed($role, $resource, $privilege);            
    }
    /**
     * Возвращает существование родителя 
     *
     * @param Zend_Acl_Resource_Interface|string|array $resource
     * @return boolean
     */
    public function hasParent($resource)
    {
        try {
            $resourceId = $this->get($resource)->getResourceId();
        } catch (Zend_Acl_Exception $e) {
            throw $e;
        }        
        if (null !== $this->_resources[$resourceId]['parent']) {
            return true;
        }
        return false;
    }
    /**
     * Возвращает родителя
     *
     * @param Zend_Acl_Resource_Interface|string|array $resource
     * @return Zend_Acl_Resource_Interface
     */
    public function getParent($resource)
    {
        try {
            $resourceId = $this->get($resource)->getResourceId();
        } catch (Zend_Acl_Exception $e) {
            throw $e;
        }
        $parent = $this->_resources[$resourceId]['parent'];    
        return $parent;          
    }
    /**
     * Возвращает существование детей 
     *
     * @param Zend_Acl_Resource_Interface|string|array $resource
     * @return boolean
     */    
    public function hasChildren($resource)
    {
        try {
            $resourceId = $this->get($resource)->getResourceId();
        } catch (Zend_Acl_Exception $e) {
            throw $e;
        }        
        if (0 !== count($this->_resources[$resourceId]['children'])) {
            return true;
        }
        return false;
    }
    /**
     * Возвращает детей
     *
     * @param Zend_Acl_Resource_Interface|string|array $resource
     * @return array
     */
    public function getChildren($resource)
    {
        try {
            $resourceId = $this->get($resource)->getResourceId();
        } catch (Zend_Acl_Exception $e) {
            throw $e;
        }     
        return array_keys($this->_resources[$resourceId]['children']);   
    }
    /**
     * Возвращает все ресурсы
     *
     * @return array
     */
    public function getAll()
    {
        return array_keys($this->_resources);
    }
    /**
     * Возвращает acl из реестра
     *
     * @return Core_Acl
     */
    public static function getRegisrty()
    {
        if (!Zend_Registry::isRegistered('acl')) {
            Zend_Registry::set('acl', new Core_Acl());
        } 
        return Zend_Registry::get('acl');
    }
    /**
     * Получает доступные привилегии
     *
     * @param Zend_Acl_Role_Interface|string|array $role
     * @param Core_Acl_Resource_Abstract|string|array $resource
     * @param string $privilegeName
     * @return array|false
     */
    public function getAllowPrivileges($role, $resource, $privilegeName = null)
    {
        $resource = $this->get($resource);
        if (!($resource instanceof Core_Acl_Resource_Abstract)) {
        	throw new Core_Exception('badTypeOfResource#');
        }
        $role = $this->getRole($role);
        $privileges = $resource->getAllPrivileges();
        if (isset($privilegeName)) {
            if (isset($privileges[$privilegeName])) {
                $privilege = $privileges[$privilegeName];
                if ($this->isAllowed($role, $resource, $privilege['id'])) {
            	   return $privileges[$privilegeName];
                }
            }
            return false;
        }
        $allowPrivileges = array();            
        foreach ($privileges as $name => $value) {
            if ($this->isAllowed($role, $resource, $value['id'])) {
                $allowPrivileges[$name] = $value;
            }
        }
        if (empty($allowPrivileges)) {
        	return false;
        }
        return $allowPrivileges;
    }
}